package smallfrench.moteur.biblio;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

import smallfrench.moteur.biblio.err.ErreurMath;
import smallfrench.moteur.biblio.err.ErreurParametres;
import smallfrench.moteur.interp.RObjet;
import smallfrench.moteur.interp.err.RException;

/**
 * Nombre décimal à très grande précision, modélisé avec un
 * {@link BigDecimal}.
 * @author antoine1023
 *
 */
public class RDecimal extends RObjet {

	public final BigDecimal valeur;

	/**
	 * MathContext proche de celui qu’on utilise à l’école.
	 */
	public static final MathContext MATH_CONTEXT =
			new MathContext(30, RoundingMode.HALF_UP);

	public static final RDecimal ZERO = new RDecimal(BigDecimal.ZERO);

	public static final RDecimal UN = new RDecimal(BigDecimal.ONE);

	public static final RDecimal PI = creer(
			"3.14159265358979323846264338327950288419716" +
			"93993751058209749445923078164062");

	public static final RDecimal E = creer(
			"2.71828182845904523536028747135266249775" +
			"72470936999595749669676277240766");

	//------------------------------------------------------------------------

	/**
	 * @param s nombre avec une virgule, pas un point.
	 * @return Le nouveau {@link RDecimal}
	 */
	public static RDecimal creer(String s) {
		try {
			BigDecimal d = new BigDecimal(s.replace(',', '.'));
			return new RDecimal(d);
		} catch (NumberFormatException e) {
			throw ErreurParametres.nouvelleException();
		}
	}

	public RDecimal(long valeur) {
		super(RClasseDecimal.CLASSE);
		this.valeur = new BigDecimal(valeur);
	}

	public RDecimal(double valeur) {
		super(RClasseDecimal.CLASSE);
		this.valeur = new BigDecimal(valeur);
	}

	public RDecimal(BigDecimal valeur) {
		super(RClasseDecimal.CLASSE);
		this.valeur = valeur;
	}

	public double enDouble() {
		return valeur.doubleValue();
	}

	//------------------------------------------------------------------------

	@Override
	public boolean equals(Object objet) {
		if (objet instanceof RDecimal) {
			return valeur.compareTo(((RDecimal)objet).valeur) == 0;
		}
		return false;
	}


	//------------------------------------------- Opérateurs binaires

	@Override
	public RObjet operateurPlusBase(RObjet o) {

		if (o instanceof RDecimal) {
			BigDecimal d = ((RDecimal)o).valeur;
			return new RDecimal(valeur.add(d, MATH_CONTEXT));

		} else if (o instanceof RFlottant) {
			double f = ((RFlottant)o).valeur;
			return new RDecimal(valeur.add(new BigDecimal(f), MATH_CONTEXT));

		} else if (o instanceof REntier) {
			long f = ((REntier)o).valeur;
			return new RDecimal(valeur.add(new BigDecimal(f), MATH_CONTEXT));

		} else if (o instanceof RChaine) {
			return new RChaine(valeur + ((RChaine)o).valeur);
		}

		return null;
	}

	@Override
	public RObjet operateurMoinsBase(RObjet o) {

		if (o instanceof RDecimal) {
			BigDecimal d = ((RDecimal)o).valeur;
			return new RDecimal(valeur.subtract(d, MATH_CONTEXT));

		} else if (o instanceof RFlottant) {
			double f = ((RFlottant)o).valeur;
			return new RDecimal(valeur.subtract(
					new BigDecimal(f), MATH_CONTEXT));

		} else if (o instanceof REntier) {
			long f = ((REntier)o).valeur;
			return new RDecimal(valeur.subtract(
					new BigDecimal(f), MATH_CONTEXT));
		}
		return null;
	}

	@Override
	public RObjet operateurEtoileBase(RObjet o) {

		if (o instanceof RDecimal) {
			BigDecimal d = ((RDecimal)o).valeur;
			return new RDecimal(valeur.multiply(d, MATH_CONTEXT));

		} else if (o instanceof RFlottant) {
			double f = ((RFlottant)o).valeur;
			return new RDecimal(valeur.multiply(
					new BigDecimal(f), MATH_CONTEXT));

		} else if (o instanceof REntier) {
			long f = ((REntier)o).valeur;
			return new RDecimal(valeur.multiply(
					new BigDecimal(f), MATH_CONTEXT));

		}
		return null;
	}

	@Override
	public RObjet operateurSlashBase(RObjet o) throws RException {

		if (o instanceof RDecimal) {

			BigDecimal d = ((RDecimal)o).valeur;

			if (d.compareTo(BigDecimal.ZERO) == 0)
				throw ErreurMath.nouvelleDivisionPar0();

			return new RDecimal(valeur.divide(d, MATH_CONTEXT));

		} else if (o instanceof REntier) {

			long v = ((REntier)o).valeur;
			if (v == 0L)
				throw ErreurMath.nouvelleDivisionPar0();

			return new RDecimal(valeur.divide(
					new BigDecimal(v), MATH_CONTEXT));

		} else if (o instanceof RFlottant) {
			double f = ((RFlottant)o).valeur;
			if (f == 0.0)
				throw ErreurMath.nouvelleDivisionPar0();

			return new RDecimal(valeur.divide(
					new BigDecimal(f), MATH_CONTEXT));
		}

		return null;
	}

	@Override
	public RObjet operateurModuloBase(RObjet o) throws RException {

		if (o instanceof RDecimal) {

			BigDecimal d = ((RDecimal)o).valeur;

			if (d.compareTo(BigDecimal.ZERO) == 0)
				throw ErreurMath.nouvelleModuloPar0();

			return new RDecimal(valeur.remainder(d, MATH_CONTEXT));

		} else if (o instanceof REntier) {

			long v = ((REntier)o).valeur;
			if (v == 0L)
				throw ErreurMath.nouvelleModuloPar0();

			return new RDecimal(valeur.remainder(
					new BigDecimal(v), MATH_CONTEXT));

		} else if (o instanceof RFlottant) {
			double f = ((RFlottant)o).valeur;
			if (f == 0.0)
				throw ErreurMath.nouvelleModuloPar0();

			return new RDecimal(valeur.remainder(
					new BigDecimal(f), MATH_CONTEXT));
		}

		return null;
	}


	//----------------------------------------------------------- Comparaisons


	@Override
	public RBooleen operateurEgalBase(RObjet o) {

		if (o instanceof RDecimal) {
			BigDecimal d = ((RDecimal)o).valeur;
			return RBooleen.nouveau(valeur.compareTo(d) == 0);

		} else if (o instanceof REntier) {
			long f = ((REntier)o).valeur;
			return RBooleen.nouveau(valeur.compareTo(new BigDecimal(f)) == 0);
		}
		return null;
	}



	@Override
	public RBooleen operateurInegalBase(RObjet o) {

		if (o instanceof RDecimal) {
			BigDecimal d = ((RDecimal)o).valeur;
			return RBooleen.nouveau(valeur.compareTo(d) != 0);

		} else if (o instanceof REntier) {
			long f = ((REntier)o).valeur;
			return RBooleen.nouveau(valeur.compareTo(new BigDecimal(f)) != 0);
		}
		return null;
	}



	@Override
	public RBooleen operateurInfBase(RObjet o) {

		if (o instanceof RDecimal) {
			BigDecimal d = ((RDecimal)o).valeur;
			return RBooleen.nouveau(valeur.compareTo(d) == -1);

		} else if (o instanceof RFlottant) {
			double f = ((RFlottant)o).valeur;
			return RBooleen.nouveau(
					valeur.compareTo(new BigDecimal(f)) == -1);

		} else if (o instanceof REntier) {
			long f = ((REntier)o).valeur;
			return RBooleen.nouveau(
					valeur.compareTo(new BigDecimal(f)) == -1);
		}
		return null;
	}



	@Override
	public RBooleen operateurSupBase(RObjet o) {

		if (o instanceof RDecimal) {
			BigDecimal d = ((RDecimal)o).valeur;
			return RBooleen.nouveau(valeur.compareTo(d) == 1);

		} else if (o instanceof RFlottant) {
			double f = ((RFlottant)o).valeur;
			return RBooleen.nouveau(
					valeur.compareTo(new BigDecimal(f)) == 1);

		} else if (o instanceof REntier) {
			long f = ((REntier)o).valeur;
			return RBooleen.nouveau(
					valeur.compareTo(new BigDecimal(f)) == 1);
		}
		return null;
	}



	@Override
	public RBooleen operateurInfegalBase(RObjet o) {

		if (o instanceof RDecimal) {
			BigDecimal d = ((RDecimal)o).valeur;
			return RBooleen.nouveau(valeur.compareTo(d) <= 0);

		} else if (o instanceof REntier) {
			long f = ((REntier)o).valeur;
			return RBooleen.nouveau(
					valeur.compareTo(new BigDecimal(f)) <= 0);
		}
		return null;
	}



	@Override
	public RBooleen operateurSupegalBase(RObjet o) {

		if (o instanceof RDecimal) {
			BigDecimal d = ((RDecimal)o).valeur;
			return RBooleen.nouveau(valeur.compareTo(d) >= 0);

		} else if (o instanceof REntier) {
			long f = ((REntier)o).valeur;
			return RBooleen.nouveau(
					valeur.compareTo(new BigDecimal(f)) >= 0);
		}

		return null;
	}

	//------------------------------------------- Opérateurs unaires

	@Override
	public RObjet operateurMoinsUnaire()
			throws RException {
		return new RDecimal(valeur.negate());
	}

	//------------------------------------------------------------------------

	@Override
	public RChaine enChaine() {
		return new RChaine(valeur.toString().replace('.', ','));
	}

	//------------------------------------------- Membres

	@Override
	public RObjet getMembre(String nom) {
		return RClasseDecimal.CLASSE.champs.get(nom);
	}

}
